查看原文
其他

黄伟亮: 探秘Linux的块设备和根

黄伟亮 Linux阅码场 2022-12-14

作者简介: 黄伟亮(Huang weller),毕业于苏州大学,就职于苏州博世汽车部件汽车多媒体事业部,从事汽车多媒体娱乐系统的平台开发工作六年有余, 接触Linux 系统近10年。感兴趣的方向有Linux系统性能优化,多媒体框架, 文件系统和存储器件, USB以及虚拟化等。


欢迎给Linuxer投稿(Linuxer只接受Linux方面的原创文章),获赠三大社任意在售图书一本,详情点击:

Linuxer-"Linux开发者自己的媒体"第二月稿件录取和赠书名单

Linuxer-"Linux开发者自己的媒体"首月稿件录取和赠书名单

感谢人民邮电异步社区对活动的大力支持!

前言



我们的学习习惯基本都是由浅入深的, 比如我们先学习如何使用fdisk工具来给磁盘分区, 之后才想到去看看fdisk到底对磁盘做了什么, 许久以后看到除了fdisk还有别的分区工具可以给磁盘分区. 通常我们只需要知道怎么用就可以了, 也有很多原因促使我们去思考它的背后到底发生了什么,这些原因可能是你碰到了具体问题, 不得不让你往下去看, 去看你认为肯定不会出问题的那一部分, 也有可能是你觉得现在的自己技术太浮于表面, 想深入一些, 也可能是受周围人的影响, 一起学习.

佛家讲究因果, 使用者往往接触的是果, 因为使用者在乎的是可用性, 开发者或者说设计者则要考虑因, 因为什么样的因就会导致什么样的果, 因为这样的设计, 所以就有那样的bug, 就像黑客帝国中的Neo 要找到architecture!

本文由来于心中的两个疑问,即平凡的存储器件是怎么从分区变成一个个块设备的, 根是怎么被mount.

Romcode 说



通常,processor上电后, 最早执行的代码是固化在CPU ROM中的程序, 它其实就是最早被执行的bootloader, 它的终极目的是从存储介质(包含uart, USB)加载另外一个bootloader, 比如u-boot. 拿启动介质是eMMC或者SD的例子来说, romcode一般会从offset = sector_size * 1的位置开始读取程序, 这么做的原因其实是为了跳开分区表

当然笔者也见过一个奇葩的例子, 海思的一款ARM9的芯片, ROMCODE直接从eMMC data partition offset 0的位置开始读取程序, 这就导致当你从emmc 上的u-boot启动, 兴冲冲的进到ramdiskeMMC进行分区, 格式化, 烧写系统之后, 发现系统再也起不来了, 那是因为分区表已经覆盖了第一个分区,破坏了bootloader.  整个系统没有使用分区工具来划分分区, 而是通过u-bootboot arguments描述分区信息. 不管怎样, 本文描述的是前者,而非这个特例.

从分区表到块设备: 我不是表, 我是块设备



Linux Storage device 通常是作为块设备被访问的,例如mtdblock, mmcblock 和宇宙第一强的nandblk 块设备(实在太崇拜该设备了,NAND发挥到了极致).  Storage device的设备驱动作为底层支撑, 负责注册块设备并且直接和存储器件打交道, 接受, 执行和响应块设备层的过来的访问请求.  比如说,  一次文件读取的操作会变成文件系统提交到块设备的块读取请求, 该块设备的读访问请求, 在块设备驱动和Host controller driver会把它被转化为MMC协议的CMD17或者CMD18指令给到EMMC物理设备.

Storage device的设备驱动注册到块设备层,并且扫描分区表, 识别分区表, 然后解析分区表, 把磁盘上的分区注册为块设备. 所以, 当你奇怪为什么没有mmcblkp0这个设备时其实不是没有mmcblk0p0,而是p0被定义为就是mmcblk0. 代表了整个磁盘. 下面从代码上来看一下一个块设备驱动的初始化过程:

根: 木叶的根



我们知道u-bootboot argument 会把root device的设备名称带给内核, 例如通过参数root=/dev/mmcblk0p2来告知内核, 根目录所在的分区.  事情真的那么简单吗?

原来这里面有一点点小弯弯:

原来在天地混沌的时候, 内核已经为了自己的未来初始化了一个ramfs, 并在ramfs中创建了一些必要的目录和设备节点, 例如/dev , /dev/console, /root.

然后, 为了mount mmcblk0p2, 它又根据设备文件名/dev/mmcblk0p2查找设备变量, ramfs下创建设备节点/dev/root, 这个设备文件指向的就是设备mmcblk0p2. 然后把ramfs下的设备/dev/mmcblk0p2挂载到ramfs/root. 切换当前路径到ramfs/root. 至此已经完成了设备mmcblk0p2的挂载工作, 但是此时它还不是根.

接着, 内核挂载devtmpfsdev目录, 然后调用sys_chroot(“.”)change root 到当前路径, 也就是ramfs/root.

反映到代码上:

因此在系统起来后, cat /proc/mounts, 我们可以看到以下信息:

可以看到有一个文件系统的类型是rootfs, 它被mount到了“/”.

这个文件系统在init/do_mount.c中被定义, 它就是ramfs的一个实例,

该类型的文件系统在init_rootfs()中被注册.


mount命令的输出上, 我们可以看到/dev/mmcblk0p2被挂载在了”/”, 殊不知这里的根已经不是原来的根.

结束语



本文没有对代码的细节作过多的分析, 一方面本文不是为了做代码分析的, 另一方面网络上有很多朋友也做过块设备的代码分析, 本文罗列了代码的脉络是为了来更好的表达分析的结果. Block layer是个很复杂的子系统, 有很多关于它的内容,比如IO-schecduler,  buffer cache,  相信自己可以越来越深入的研究这个子系统.

iphone用户扫描二维码打赏,所有赏金将由我转发给作者黄伟亮。

Android用户点击“赞赏”按钮

Android用户打赏

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存